<template>
  <div
    :class="[prefixCls]"
    v-click-outside="onClickoutside"
    @mouseenter="handleMouseenter"
    @mouseleave="handleMouseleave"
  >
    <div
      :class="relClasses"
      ref="reference"
      @click="handleClick"
      @contextmenu.prevent="handleRightClick"
    >
      <slot></slot>
    </div>
    <Drop
      ref="drop"
      :visible="currentVisible"
      :classes="dropdownCls"
      :placement="placement"
      :eventsEnabled="eventsEnabled"
      :boundariesElement="boundariesElement"
      :transfer="transfer"
      transition-name="transition-drop"
      @mouseenter="handleMouseenter"
      @mouseleave="handleMouseleave">
      <slot name="list"></slot>
    </Drop>
  </div>
</template>
<script>
import { nextTick, getCurrentInstance } from 'vue'
import Drop from '../select/dropdown.vue'
import clickOutside from '../../directives/clickoutside'
import { oneOf, findComponentUpward } from '../../utils/assist'

const prefixCls = 'haloe-dropdown'

export default {
  name: 'Dropdown',
  directives: { clickOutside },
  components: { Drop },
  emits: [
    'on-visible-change',
    'on-clickoutside',
    'on-click',
    'on-hover-click',
    'on-haschild-click',
  ],
  props: {
    trigger: {
      validator(value) {
        return oneOf(value, ['click', 'hover', 'custom', 'contextMenu'])
      },
      default: 'hover',
    },
    placement: {
      validator(value) {
        return oneOf(value, [
          'top',
          'top-start',
          'top-end',
          'bottom',
          'bottom-start',
          'bottom-end',
          'left',
          'left-start',
          'left-end',
          'right',
          'right-start',
          'right-end',
        ])
      },
      default: 'bottom',
    },
    visible: {
      type: Boolean,
      default: false,
    },
    transfer: {
      type: Boolean,
      default() {
        const global = getCurrentInstance().appContext.config.globalProperties
        return !global.$HALOE || global.$HALOE.transfer === ''
          ? false
          : global.$HALOE.transfer
      },
    },
    transferClassName: {
      type: String,
    },
    stopPropagation: {
      type: Boolean,
      default: false,
    },
    // 4.0.0
    capture: {
      type: Boolean,
      default() {
        const global = getCurrentInstance().appContext.config.globalProperties
        return !global.$HALOE ? true : global.$HALOE.capture
      },
    },
    // 4.6.0
    eventsEnabled: {
      type: Boolean,
      default: false,
    },
    boundariesElement: {
      // type: [String, HTMLElement],
      default: 'window',
    },
  },
  computed: {
    transition() {
      return ['bottom-start', 'bottom', 'bottom-end'].indexOf(this.placement) >
        -1
        ? 'slide-up'
        : 'fade'
    },
    dropdownCls() {
      return {
        [prefixCls + '-transfer']: this.transfer,
        [this.transferClassName]: this.transferClassName,
      }
    },
    relClasses() {
      return [
        `${prefixCls}-rel`,
        {
          [`${prefixCls}-rel-user-select-none`]: this.trigger === 'contextMenu',
        },
      ]
    },
  },
  data() {
    return {
      prefixCls: prefixCls,
      currentVisible: this.visible,
      timeout: null,
    }
  },
  watch: {
    visible(val) {
      this.currentVisible = val
    },
    currentVisible(val) {
      if (val) {
        this.$refs.drop.update()
      } else {
        this.$refs.drop.destroy()
      }
      this.$emit('on-visible-change', val)
    },
  },
  methods: {
    handleClick() {
      if (this.trigger === 'custom') return false
      if (this.trigger !== 'click') {
        return false
      }
      // #661
      const $parent = this.hasParent()
      if (!$parent) this.currentVisible = !this.currentVisible
    },
    handleRightClick() {
      if (this.trigger === 'custom') return false
      if (this.trigger !== 'contextMenu') {
        return false
      }
      this.currentVisible = !this.currentVisible
    },
    handleMouseenter() {
      if (this.trigger === 'custom') return false
      if (this.trigger !== 'hover') {
        return false
      }
      if (this.timeout) clearTimeout(this.timeout)
      this.timeout = setTimeout(() => {
        this.currentVisible = true
      }, 250)
    },
    handleMouseleave() {
      if (this.trigger === 'custom') return false
      if (this.trigger !== 'hover') {
        return false
      }
      if (this.timeout) {
        clearTimeout(this.timeout)
        this.timeout = setTimeout(() => {
          this.currentVisible = false
        }, 150)
      }
    },
    onClickoutside(e) {
      this.handleClose()
      this.handleRightClose()
      if (this.currentVisible) this.$emit('on-clickoutside', e)
    },
    handleClose() {
      if (this.trigger === 'custom') return false
      if (this.trigger !== 'click') {
        return false
      }
      this.currentVisible = false
    },
    handleRightClose() {
      if (this.trigger === 'custom') return false
      if (this.trigger !== 'contextMenu') {
        return false
      }
      this.currentVisible = false
    },
    hasParent() {
      const $parent = findComponentUpward(this, 'Dropdown')
      if ($parent) {
        return $parent
      } else {
        return false
      }
    },
    handleHaschildClick() {
      nextTick(() => {
        if (this.trigger === 'custom') return false
        this.currentVisible = true
      })
      const $parent = this.hasParent()
      if ($parent) $parent.handleHaschildClick()
    },
    handleItemClick(key) {
      if (this.stopPropagation) return
      const $parent = this.hasParent()
      if ($parent) $parent.handleItemClick(key)
      else this.$emit('on-click', key)
    },
    handleHoverClick() {
      const $parent = this.hasParent()
      if ($parent) {
        nextTick(() => {
          if (this.trigger === 'custom') return false
          this.currentVisible = false
        })
        $parent.handleHoverClick()
      } else {
        nextTick(() => {
          if (this.trigger === 'custom') return false
          this.currentVisible = false
        })
      }
    },
  },
}
</script>
